//
// Copyright 2022 The Sparrow Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

/**
 * @brief Read text data line by line,
 *        Line number starts from 1.
 *
 * Example 1:
 *
 * TextFileReader reader;
 * if (reader.open(file_path)) {
 *     for (const auto& ctx : reader) {
 *         ctx.Data();
 *         ctx.Line();
 *     }
 * }
 *
 * Example 2:
 * TextDataReader reader(buff, len);
 * for (const auto& ctx : reader) {
 *     ctx.Data();
 *     ctx.Line();
 * }
 *
 */
#ifndef SPARROW_INCLUDE_TEXT_READER_H_
#define SPARROW_INCLUDE_TEXT_READER_H_

#include <stddef.h>
#include <stdint.h>
#include <fstream>
#include <sstream>
#include <string>

namespace sparrow {
namespace internal {
class NextLineInterface {

public:
    virtual ~NextLineInterface() = default;

    virtual bool NextLine(std::string *data, uint32_t *line) = 0;
};

} // namespace internal

class ALineOfData final {
    friend class LineIterator;
    friend class TextDataReader;
    friend class TextFileReader;

public:
    ALineOfData(/* args */)
        : line_(0) {}
    ~ALineOfData() {}

    uint32_t Line() const { return line_; }
    const std::string &Data() const { return data_; }

private:
    uint32_t line_;
    std::string data_;
};

class LineIterator final {

public:
    LineIterator(/* args */)
        : reader_(nullptr)
        , end_of_file_(true) {}
    LineIterator(internal::NextLineInterface *reader, bool eof)
        : reader_(reader)
        , end_of_file_(eof) {}
    LineIterator(internal::NextLineInterface *reader, const ALineOfData &data, bool eof)
        : reader_(reader)
        , end_of_file_(eof)
        , one_line_data_(data) {}

    ~LineIterator() {}

    bool operator==(const LineIterator &oth) {
        if (reader_ == oth.reader_ && end_of_file_ == oth.end_of_file_ &&
            one_line_data_.line_ == oth.one_line_data_.line_) {
            return true;
        }
        return false;
    }
    bool operator!=(const LineIterator &oth) { return !this->operator==(oth); }

    LineIterator &operator++() {
        if (reader_) {
            end_of_file_ = !reader_->NextLine(&one_line_data_.data_, &one_line_data_.line_);
        }
        return *this;
    }
    const LineIterator operator++(int) {
        LineIterator ret = *this;
        if (reader_) {
            end_of_file_ = !reader_->NextLine(&one_line_data_.data_, &one_line_data_.line_);
        }
        return ret;
    }

    ALineOfData &operator*() { return one_line_data_; }
    ALineOfData *operator->() { return &one_line_data_; }

private:
    internal::NextLineInterface *reader_;
    bool end_of_file_;
    ALineOfData one_line_data_;
};

class TextFileReader final : public internal::NextLineInterface {
    friend class LineIterator;

public:
    TextFileReader() = default;
    ~TextFileReader() = default;

    TextFileReader(const TextFileReader &) = delete;
    TextFileReader &operator=(const TextFileReader &) = delete;

    bool open(const std::string &path) {
        stream_.open(path);
        return stream_.is_open();
    }
    LineIterator begin() {
        ALineOfData data;
        bool eof = !NextLine(&data.data_, &data.line_);
        return LineIterator(this, data, eof);
    }
    LineIterator end() { return LineIterator(this, true); }

private:
    bool NextLine(std::string *data, uint32_t *line) override {
        if (!stream_.is_open() || stream_.eof()) {
            data->clear();
            *line = 0;
            return false;
        }

        (*line)++;

        std::string temp;
        std::getline(stream_, temp);
        data->swap(temp);
        return true;
    }
    std::ifstream stream_;
};

class TextDataReader final : public internal::NextLineInterface {
    friend class LineIterator;

public:
    TextDataReader(const char *data, size_t len) { stream_ << std::string(data, len); }
    ~TextDataReader() = default;

    TextDataReader(const TextDataReader &) = delete;
    TextDataReader &operator=(const TextDataReader &) = delete;

    LineIterator begin() {
        ALineOfData data;
        bool eof = !NextLine(&data.data_, &data.line_);
        return LineIterator(this, data, eof);
    }
    LineIterator end() { return LineIterator(this, true); }

private:
    bool NextLine(std::string *data, uint32_t *line) override {
        if (stream_.eof()) {
            data->clear();
            *line = 0;
            return false;
        }

        (*line)++;

        std::string temp;
        std::getline(stream_, temp);
        data->swap(temp);

        return true;
    }
    std::stringstream stream_;
};
} // namespace sparrow

#endif // SPARROW_INCLUDE_TEXT_READER_H_
